/**
 * 佛祖镇楼                  BUG辟易
 * 佛曰:
 * 写字楼里写字间，写字间里程序员；
 * 程序人员写程序，又拿程序换酒钱。
 * 酒醒只在网上坐，酒醉还来网下眠；
 * 酒醉酒醒日复日，网上网下年复年。
 * 但愿老死电脑间，不愿鞠躬老板前；
 * 奔驰宝马贵者趣，公交自行程序员。
 * 别人笑我忒疯癫，我笑自己命太贱；
 * 不见满街漂亮妹，哪个归得程序员？
 */
package com.wolfking.spi;

import javassist.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.UUID;

/**
 * 经过隔壁老王处理的Javassist生成子类实现的
 * spi service的动态代理
 * Object的方法调用this的方法
 * 其他方法根据SpiApplicationContext的Threadlocal动态选择Service
 *
 * @author 赵伟伟(wolfking)
 * created on 2019-09-29 13:33
 */
//@SuppressWarnings("all")
public abstract class AbstractJavassistSpiProxy {

    /*slf4j日志*/
    protected Logger logger = LoggerFactory.getLogger(getClass());
    /*spring上下文的bean的映射*/
    private Map beans;
    /*接口上的注解*/
    private SpiInterface spiInterfaceAnnotation;

    /**
     * 只能在子类中使用
     *
     * @return
     */
    protected Object getCurrentSpiService() {
        String code = SpiApplicationContext.getSpiCode();
        if (logger.isDebugEnabled()) {
            logger.debug("current spi service code is {}", code);
        }
        return beans.get(StringUtils.isEmpty(code) ? spiInterfaceAnnotation.defaultCode() : code);
    }

    public void setBeans(Map beans) {
        this.beans = beans;
    }

    public void setSpiInterfaceAnnotation(SpiInterface spiInterfaceAnnotation) {
        this.spiInterfaceAnnotation = spiInterfaceAnnotation;
    }

    /**
     * 创建javassit的继承类
     *
     * @param spiInterface spi的接口
     * @param beans        spring的实体
     * @param <T>          泛型规约
     * @return
     * @throws Exception
     */
    public static <T> T getSpiProxy(Class<T> spiInterface, Map beans) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.appendClassPath(new ClassClassPath(spiInterface));
        pool.appendClassPath(new ClassClassPath(AbstractJavassistSpiProxy.class));
        /*代理类都是有$开头或者结尾，生成类名的时候最好带上随机码，避免出现class frozen的情况*/
        CtClass cc = pool.makeClass(spiInterface.getName() + "$" + Math.abs(UUID.randomUUID().hashCode()));
        /*添加继承的接口和抽象类*/
        cc.setInterfaces(new CtClass[]{pool.get(spiInterface.getName())});
        cc.setSuperclass(pool.get(AbstractJavassistSpiProxy.class.getName()));
        Method[] methods = spiInterface.getMethods();
        String body = "%s ((%s)getCurrentSpiService()).%s(%s);";
        /*循环method,逐个添加*/
        for (Method method : methods) {
            if (method.isDefault()) {
                continue;
            }
            pool.appendClassPath(new ClassClassPath(method.getReturnType()));
            Parameter[] parameters = method.getParameters();
            CtClass[] classes = new CtClass[method.getParameterCount()];
            StringBuilder args = new StringBuilder();
            for (int j = 0, size = parameters.length; j < size; j++) {
                pool.appendClassPath(new ClassClassPath(parameters[j].getType()));
                classes[j] = pool.get(parameters[j].getType().getName());
                args.append("$").append(j + 1).append(j != size - 1 ? "," : "");
            }
            String methodBody = String.format(body, method.getReturnType() == Void.TYPE ? "" : "return", spiInterface.getName(), method.getName(), args.toString());
            CtMethod ctMethod = new CtMethod(pool.get(method.getReturnType().getName()), method.getName(), classes, cc);
            /*interface 的接口都是 public类型的*/
            ctMethod.setModifiers(Modifier.PUBLIC);
            ctMethod.setBody(methodBody);
            cc.addMethod(ctMethod);
        }
        /*创建新的实例*/
        AbstractJavassistSpiProxy proxy = (AbstractJavassistSpiProxy) cc.toClass().newInstance();
        proxy.setSpiInterfaceAnnotation(spiInterface.getAnnotation(SpiInterface.class));
        proxy.setBeans(beans);
        return spiInterface.cast(proxy);
    }
}
